#!/usr/bin/env python3

"""
Program used to convert mpc files to mp3.

Licence: GPLv2
Author: Joseph Botosh <rumly111@gmail.com>
"""

import mpcchap
import os, sys
import tempfile
import getopt
import subprocess
import logging

USAGE='''\
Usage:
    {} [OPTIONS]
Option:
    [  -h | --help ]
    [ [-m | --mpc ]   <source-mpc>
    [ [-n | --track ] <track-number>
    [ [-d | --dir ]   <destination-dir>
    [ [-t | --type ] mp3 | ogg | mpc
    [  -i | --interactive ]
Notes:
    If --track not given, then all the songs will be converted
    If --dir not given, then the default '/tmp' will be used
    If --interactive is given, not set options will be asked for
    If --type is not given, it defaults to mp3
'''.format(sys.argv[0])

class MpcConverterError(Exception):
    pass

def escape(s : str) -> str:
    trans = str.mktrans({"'":"\\'",
                         '"':'\\"',
                         '/':'_',})
    return s.translate(trans)

class MpcConverter(mpcchap.MpcChapters):
    def __init__(self, mpcfile : str):
        mpcchap.MpcChapters.__init__(self)

        if not mpcfile.endswith('.mpc'):
            raise MpcConverterError('Invalid filename format: {}'.format(mpcfile))
        elif not os.path.exists(mpcfile):
            raise MpcConverterError('MPC file "{}" does not exist !'.format(mpcfile))
                
        self.mpcfile = mpcfile
        self.read_chapters_from(mpcfile)
        
    def convert_to_mp3(self, song_num : int, outdir='/tmp'):
        '''
        Convert song number <song_num> to output directory <outdir>
        '''
        if not 1 <= song_num <= len(self.chapters):
            raise MpcConverterError('song number {0} is out of range (1,{1})'.\
                                    format(song_num, len(self.chapters)))

        chapter = self.chapters[song_num-1]

        artist  = chapter.get('Artist','')
        album   = chapter.get('Album','')
        year    = chapter.get('Date','0')
        title   = chapter.get('Title','')
        comment = chapter.get('Comment','')
        track   = chapter.get('Track','1')
        genre   = chapter.get('Genre','')

        mpc_decoder = 'mpcdec'
        mp3_encoder = 'lame'
        mpc_cutter  = 'mpccut'
        
        encoding_opts = ['--resample','44.1','-b','256']
        tagging_opts  = ['--add-id3v2']
        if title:    tagging_opts.extend(['--tt',title])
        if artist:   tagging_opts.extend(['--ta',artist])
        if album:    tagging_opts.extend(['--tl',album])
        if year:     tagging_opts.extend(['--ty',year])
        if comment:  tagging_opts.extend(['--tc',comment])
        if track:    tagging_opts.extend(['--tn',track])
        if genre:    tagging_opts.extend(['--tg',genre])

        # TODO make outfile format configurable
        outfile = os.path.join(outdir,'{0} - {1}.mp3'.\
                                       format(artist,title).replace('/','_'))

        mpc_tmp = tempfile.mktemp(suffix='.mpc',prefix='mpcc-')
        if song_num == len(self.chapters):
            cutting_opts=['-s',str(self.start_samples[song_num-1]),
                          self.mpcfile, mpc_tmp]
        else:
            cutting_opts=['-s',str(self.start_samples[song_num-1]),
                          '-e',str(self.start_samples[song_num]),
                          self.mpcfile, mpc_tmp]

        cutting_args = [mpc_cutter] + cutting_opts
        logging.info(' '.join(cutting_args))
        devnull = open('/dev/null',mode='wb')
        result = subprocess.call (args=cutting_args,
                                  stdout=devnull,
                                  stderr=devnull)

        # TODO: if error occurs, tempfiles will not be deleted
        if result != 0: raise MpcConverterError('mpccut error')

        mpc_dec_args = [mpc_decoder,mpc_tmp,'-']
        mp3_enc_args = [mp3_encoder]+tagging_opts+encoding_opts+['-',outfile]
        logging.info(' '.join(mpc_dec_args) + ' | ' + ' '.join(mp3_enc_args))
        
        mpc_dec_proc = subprocess.Popen([mpc_decoder,mpc_tmp,'-'],
                                        stdout=subprocess.PIPE,
                                        stderr=devnull)
        mp3_enc_proc_res = subprocess.call(mp3_enc_args,
                                           stdin=mpc_dec_proc.stdout,
                                           stderr=devnull)
        if mp3_enc_proc_res != 0: raise MpcConverterError('mpcdec or lame error')

##        logging.info('Converting complete...')

        os.remove(mpc_tmp)

    def convert_to_ogg(self, song_num : int, outdir='/tmp'):
        '''
        Convert song number <song_num> to output directory <outdir>
        '''
        if not 1 <= song_num <= len(self.chapters):
            raise MpcConverterError('song number {0} is out of range (1,{1})'.\
                                    format(song_num, len(self.chapters)))

        chapter = self.chapters[song_num-1]

        artist  = chapter.get('Artist','')
        album   = chapter.get('Album','')
        year    = chapter.get('Date','0')
        title   = chapter.get('Title','')
        comment = chapter.get('Comment','')
        track   = chapter.get('Track','1')
        genre   = chapter.get('Genre','')

        mpc_decoder = 'mpcdec'
        ogg_encoder = 'oggenc'
        mpc_cutter  = 'mpccut'

        encoding_opts = ['--quiet','--quality','7']
        tagging_opts = []

        if title:    tagging_opts.extend(['--title',title])
        if artist:   tagging_opts.extend(['--artist',artist])
        if album:    tagging_opts.extend(['--album',album])
        if year:     tagging_opts.extend(['--date',year])
        if comment:  tagging_opts.extend(['--comment', 
                                          'COMMENT={}'.format(comment)])
        if track:    tagging_opts.extend(['--tracknum',track])
        if genre:    tagging_opts.extend(['--genre',genre])

        outfile = os.path.join(outdir,'{0} - {1}.ogg'.\
                                       format(artist,title).replace('/','_'))

        mpc_tmp = tempfile.mktemp(suffix='.mpc',prefix='mpcc-')
        if song_num == len(self.chapters):
            cutting_opts=['-s',str(self.start_samples[song_num-1]),
                          self.mpcfile, mpc_tmp]
        else:
            cutting_opts=['-s',str(self.start_samples[song_num-1]),
                          '-e',str(self.start_samples[song_num]),
                          self.mpcfile, mpc_tmp]

        cutting_args = [mpc_cutter] + cutting_opts
        logging.info(' '.join(cutting_args))
        devnull = open('/dev/null',mode='wb')
        result = subprocess.call (args=cutting_args,
                                  stdout=devnull,
                                  stderr=devnull)

        # TODO: if error occurs, tempfiles will not be deleted
        if result != 0: raise MpcConverterError('mpccut error')

        mpc_dec_args = [mpc_decoder,mpc_tmp,'-']
        ogg_enc_args = [ogg_encoder]+tagging_opts+encoding_opts+['-o',outfile,'-']
        logging.info(' '.join(mpc_dec_args) + ' | ' + ' '.join(ogg_enc_args))

        mpc_dec_proc = subprocess.Popen([mpc_decoder,mpc_tmp,'-'],
                                        stdout=subprocess.PIPE,
                                        stderr=devnull)

        ogg_enc_proc_res = subprocess.call(ogg_enc_args,
                                           stdin=mpc_dec_proc.stdout,
                                           stderr=devnull)
        if ogg_enc_proc_res != 0: raise MpcConverterError('mpcdec or oggenc error')

        os.remove(mpc_tmp)

    def cut_to_mpc(self, song_num : int, outdir='/tmp'):
        '''
        Extract single mpc track
        '''
        chapter = self.chapters[song_num-1]

        artist  = chapter.get('Artist','')
        album   = chapter.get('Album','')
        year    = chapter.get('Date','0')
        title   = chapter.get('Title','')
        comment = chapter.get('Comment','')
        track   = chapter.get('Track','1')
        genre   = chapter.get('Genre','')

        mpc_cutter  = 'mpccut'

        outfile = os.path.join(outdir,'{0} - {1}.mpc'.\
                                       format(artist,title).replace('/','_'))

        mpc_tagger = 'apetag'
        tagging_opts  = ['-i',outfile,'-m','update']
        if title:    tagging_opts.extend(['-p', 'Title={}'.format(title)])
        if artist:   tagging_opts.extend(['-p', 'Artist={}'.format(artist)])
        if album:    tagging_opts.extend(['-p', 'Album={}'.format(album)])
        if year:     tagging_opts.extend(['-p', 'Year={}'.format(year)])
        if comment:  tagging_opts.extend(['-p', 'Comment={}'.format(comment)])
        if track:    tagging_opts.extend(['-p', 'Track={}'.format(track)])
        if genre:    tagging_opts.extend(['-p', 'Genre={}'.format(genre)])        

        if song_num == len(self.chapters):
            cutting_opts=['-s',str(self.start_samples[song_num-1]),
                          self.mpcfile, outfile]
        else:
            cutting_opts=['-s',str(self.start_samples[song_num-1]),
                          '-e',str(self.start_samples[song_num]),
                          self.mpcfile, outfile]

        cutting_args = [mpc_cutter] + cutting_opts
        logging.info(' '.join(cutting_args))
        devnull = open('/dev/null',mode='wb')
        result = subprocess.call (args=cutting_args,
                                  stdout=devnull,
                                  stderr=devnull)

        if result != 0: raise MpcConverterError('mpccut error')

        tagging_args = [mpc_tagger] + tagging_opts
        logging.info(' '.join(tagging_args))
        result = subprocess.call (args=tagging_args,
                                  stdout=devnull,
                                  stderr=devnull)

        if result != 0: raise MpcConverterError('apetag error')


if __name__=='__main__':
    # parse cmd-line opts here
    try:
        opts, args = getopt.getopt( sys.argv[1:],
                                   'hm:n:d:it:',
                                   ['help','mpc=',
                                    'track=','dir=',
                                    'interactive','type='])
    except getopt.GetoptError as e:
        print(USAGE)
        sys.exit(2)

    if len(sys.argv) == 1:
        print(USAGE)
        sys.exit(0)

    logging.basicConfig(level=logging.INFO,format='%(asctime)s %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')

    interactive = False
    outdir = '/tmp'
    tracks = []
    mpc_source = ''
    output_type = 'mp3'
    for o, a in opts:
        if o in   ('-h','--help'):
            print(USAGE)
            sys.exit(0)
        elif o in ('-m','--mpc'):
            mpc_source = a
        elif o in ('-n','--track'):
            tracks = [int(a)]
        elif o in ('-d','--dir'):
            outdir = a
        elif o in ('-t','--type'):
            if a in ('mp3', 'ogg', 'mpc'):
                output_type = a
            else:
                raise Exception('--type must be mp3|ogg|mpc')
        elif o in ('-i','--interactive'):
            interactive = True

    if interactive:
        print('Interactive mode.')
        if not mpc_source:  mpc_source = input('Enter full path to mpc file:')
        if not tracks:
            tracks_str=input('Enter comma-separated track numbers: ')
            if tracks_str in ('','a','all'):
                tracks=[]
            else:
                for tr_str in tracks_str.split(','):
                    tr_num = int(tr_str.strip())
                    tracks.append(tr_num)
    
    converter = MpcConverter(mpc_source)
    if tracks == []:
        tracks = range(1,len(converter.chapters))
    
    for track in tracks:
        if output_type == 'mp3':
            converter.convert_to_mp3(track, outdir)
        elif output_type == 'ogg':
            converter.convert_to_ogg(track, outdir)
        elif output_type == 'mpc':
            converter.cut_to_mpc(track, outdir)


    print("CONVERT FINISHED")
